#include "thread.h"
#include <glib.h>

#include <stdlib.h>
#include <stdio.h>

#include <ucontext.h>

// La taille par défaut de la pile d'un nouveau thread est de 2Mo
#define DEFAULT_STACK_SIZE 2048
#define DEAD 0
#define ACTIVE 1
#define TO_BE_DESTROYED 2
#define NON_ACTIVE 3

#define EINVAL 4

typedef struct {
  int id_thread;
  int state;
  int is_joined;
  ucontext_t attr;
  void * result;
}thread_info;



// thread principal
thread_info * main_thread = NULL;
// pointeur servant à la création d'un nouveau thread
thread_info * thread_to_create = NULL;
// thread en cours d'exécution
thread_info * running_thread = NULL;

// liste des threads actifs
GList * active_threads;

int nb_threads = 0;

/* recuperer l'identifiant du thread courant.
 */
thread_t thread_self(void) {
  return (thread_t)(running_thread);
}

/* thread_attr_init(ucontext_t * attr)
 * Fonction initialisant le nouveau contexte à créer à l'aide d'un modèle de
 * contexte déjà existant.
 * Si on lui donne NULL en paramètre, la fonction réalise une initialisation
 * par défaut.
 * Retourne 0 en cas d'erreur, 1 sinon.
 */
int thread_attr_init(ucontext_t * attr) {
  if (attr == NULL) {
    (*thread_to_create).attr.uc_stack.ss_size = DEFAULT_STACK_SIZE;
    (*thread_to_create).attr.uc_stack.ss_sp = malloc(DEFAULT_STACK_SIZE);
    if ((*thread_to_create).attr.uc_stack.ss_sp == NULL) {
      perror("malloc");
      return 0;
    }
    //(*thread_to_create).attr.uc_sigmask = 0;
    (*thread_to_create).attr.uc_link = &(*main_thread).attr;
  
  }
  else {
    (*thread_to_create).attr.uc_stack.ss_size = (*attr).uc_stack.ss_size;
    (*thread_to_create).attr.uc_stack.ss_sp = malloc((*thread_to_create).attr.uc_stack.ss_size);
    if (NULL == ((*thread_to_create).attr.uc_stack.ss_sp)) {
      perror("malloc");
      return 0;
    }
    //(*thread_to_create).attr.uc_sigmask = (*attr).uc_sigmask;
    (*thread_to_create).attr.uc_link = (*attr).uc_link;
  }

  return 1;
}

void thread_init (){
  // On alloue la mémoire nécessaire pour nos thread_info.
    main_thread = malloc(sizeof(thread_info));
	
    // Si c'est le premier thread qu'on crée, main_thread est NULL, et l'on doit donc le lier au contexte actuel.
    getcontext(&(main_thread->attr));
    // On le déclare non joint
    main_thread->is_joined = 1;
    // On lui donne aussi l'identifiant 0.
    main_thread->id_thread = 0;
    // On le déclare actif
    main_thread->state = ACTIVE;
    // On place le résultat NULL
    main_thread->result = NULL;
    // On indique que le thread principal est celui qui s'exécute.
    running_thread = main_thread;
    // On ajoute le thread principal à la liste des threads en exécution.
    active_threads = g_list_prepend(active_threads, main_thread);
}


/* creer un nouveau thread qui va exécuter la fonction func avec l'argument funcarg.
 * renvoie 0 en cas de succès, -1 en cas d'erreur.
 */
int thread_create(thread_t * newthread, ucontext_t * attr, void * (*func)(void *), void * funcarg) {
  //  printf("debut creation\n");
  if (main_thread == NULL) {
    thread_init();
    printf("main : %p\n", thread_self());
  }

  thread_to_create = malloc(sizeof(thread_info));
  // 
  //    (*thread_to_create).attr.uc_stack.ss_sp = malloc(DEFAULT_STACK_SIZE);

  
  thread_to_create->state = ACTIVE;
  running_thread = thread_to_create;
  thread_to_create->is_joined = 0;
  thread_to_create->id_thread = (++nb_threads); 

  *newthread = (thread_t)thread_to_create;
  getcontext(&thread_to_create->attr);
    
  // On initialise le contexte à créer.
  if (thread_attr_init(attr) == 0) {
    perror("thread_attr_init");
    return -1;      
  }
  // On ajoute le thread ainsi créé à la liste des threads en exécution.
  // On utilise append pour ajouter le nouveau thread et poursuivre l'exécution du thread courant.

  active_threads = g_list_prepend(active_threads, thread_to_create);

  // On crée le nouveau contexte.
  makecontext(&(thread_to_create->attr), (void (*)(void))func, 1, funcarg);
  swapcontext(&main_thread->attr,&(thread_to_create->attr));  

  return 0;
}


/* passer la main à un autre thread.
 */
int thread_yield(void) {
  
  if (main_thread == NULL)
    thread_init();
 
  thread_info* temp;
  temp = running_thread;

  //  printf("debut yield\n");
  // printf("id : %d\n",temp->id_thread);

  // On retire le thread de la liste des threads actifs...
  active_threads = g_list_remove(active_threads, running_thread);
  // ...pour l'y remettre à la fin.
  active_threads = g_list_append(active_threads, running_thread);
 
  // On se place dans le thread suivant.
  GList* glist = g_list_first(active_threads);
  running_thread = (thread_info*)glist->data;
 

  /*  if ((thread_info*)glist->data == main_thread)
      return 1;*/
  /*
  if (setcontext(&running_thread->attr) == -1) {
    perror("setcontext");
    return 1;
  }
  */
  swapcontext(&(temp->attr), &(running_thread->attr));
  

  return 0;
}



/* /\* attendre la fin d'exécution d'un thread. */
/*  * la valeur renvoyée par le thread est placée dans *retval. */
/*  * si retval est NULL, la valeur de retour est ignorée. */
/*  *\/ */
int thread_join(thread_t thread, void **retval) { 

  printf("debut join\n");

  if (main_thread == NULL)
    thread_init();

  thread_info* thread_to_wait = (thread_info*)thread;
   
  //getcontext(&running_thread->attr); 
  if (thread_to_wait->is_joined == 1 || thread_to_wait == running_thread)
    return EINVAL;
  //  printf("avant while\n");

  printf("thread_yield\n");

  /* running_thread->state = NON_ACTIVE;
  swapcontext(&running_thread->attr,&thread_to_wait->attr);
  running_thread->state = ACTIVE;*/
  thread_yield();
  
  printf("Debut while thread_join\n");

  // On attend que le thread à joindre soit terminé
  while (thread_to_wait->state != running_thread){
    thread_yield();
  }
  
  // setcontext(&(thread_to_wait->attr));   

  // swapcontext(&thread_to_wait->attr, &running_thread->attr);

  printf("res : %d\n", *retval);
  // On récupère la valeur de retour du thread joint
  if (retval != NULL)
   *retval = thread_to_wait->result;
  
  if (thread_to_wait->state == TO_BE_DESTROYED) {
    // On libère la ressource du thread joint
    free(thread_to_wait);
    thread_to_wait = NULL;
    return 0;
  }
 
  
  thread_to_wait->is_joined = 1;

  return 0;
}


/* terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 *
 * L'attribut noreturn aide le compilateur à optimiser le code de
 * l'application (élimination de code mort). Attention à ne pas mettre
 * cet attribut dans votre interface tant que votre thread_exit()
 * n'est pas correctement implémenté (il ne doit jamais retourner).
 */
void thread_exit(void *retval){
  // On renvoie le résultat du thread à travers retval
  running_thread->result = retval;
  running_thread->state = TO_BE_DESTROYED;
  free((running_thread->attr).uc_stack.ss_sp);
  while (1) 
    thread_yield();
  
}
